﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace ArmoryAPI
{
    /// <summary>
    /// Provides functionality to consume the Battle.net RESTful APIs
    /// </summary>
    internal class WebRequest
    {
        #region Members

            private BattleNetRegion _region;
            private BattleNetLocale _locale;

        #endregion

        #region Properties

            /// <summary>
            /// Returns the region specific portion of the API base e.g. us.battle.net
            /// </summary>
            private String APIBase
            {
                get
                {
                    switch (_region)
                    {
                        case BattleNetRegion.America:
                            return "us.battle.net";

                        case BattleNetRegion.China:
                            return "battlenet.com.cn";

                        case BattleNetRegion.Europe:
                            return "eu.battle.net";

                        case BattleNetRegion.Korea:
                            return "kr.battle.net";

                        case BattleNetRegion.Taiwan:
                            return "tw.battle.net";

                        default:
                            return "us.battle.net";
                    }
                }
            }

            /// <summary>
            /// Returns the Battle.net achievements API with the specified locale
            /// </summary>
            private String AchievementListAPI
            {
                get
                {
                    return String.Format("http://{0}/api/wow/data/character/achievements?locale={1}", this.APIBase, this._locale.ToString());
                }
            }

        #endregion

        #region Instance methods

            /// <summary>
            /// Returns the number of child tokens within a JToken
            /// </summary>
            /// <param name="token">The JToken to find children of</param>
            /// <returns>The number of child tokens within token</returns>
            private int GetTokenCount(JToken token)
            {
                int retval = 0;

                foreach (JToken child in token.Children<JToken>())
                {
                    retval++;
                }

                return retval;
            }

            /// <summary>
            /// Fetches the JSON string from the sepcified Battle.net API
            /// </summary>
            /// <param name="api">The API to create the JSON</param>
            /// <returns>The JSON document produced by the API</returns>
            private String GetJSON(String api)
            {
                String retval = String.Empty;

                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(api);

                try
                {
                    using (WebResponse response = request.GetResponse())
                    {
                        using (Stream stream = response.GetResponseStream())
                        {
                            using (StreamReader reader = new StreamReader(stream))
                            {
                                retval = reader.ReadToEnd();
                            }
                        }
                    }
                }
                catch { }

                return retval;
            }

            /// <summary>
            /// Parses the result from the Battle.net RESTful API into an Achievement.
            /// </summary>
            /// <param name="id">The unique identifier of the achievement</param>
            /// <returns>Achievement</returns>
            public Achievement GetAchievementByID(int id)
            {
                Achievement retval = new Achievement();

                foreach (Achievement achievement in this.GetAllAchievements())
                {
                    if (achievement.Equals(id))
                    {
                        retval = achievement;
                        break;
                    }
                }

                return retval;
            }

            /// <summary>
            /// Parse the Battle.net achievements API into an ICollection of Achievements
            /// </summary>
            /// <returns>An ICollection of Achievements</returns>
            public ICollection<Achievement> GetAllAchievements()
            {
                List<Achievement> retval = new List<Achievement>();

                JObject json = JObject.Parse(this.GetJSON(this.AchievementListAPI));
                JArray categories = (JArray)json["achievements"];

                // Loop through the top level categories
                foreach (JObject category in categories)
                {
                    // Loop through all the achievements directly under the category
                    foreach (JObject achievement in (JArray)category["achievements"])
                    {
                        if (achievement["title"] != null)
                        {
                            retval.Add(new Achievement((int)achievement["id"], (String)achievement["title"], (int)achievement["points"], (String)achievement["description"]));
                        }
                    }

                    // Loop through the subcategories if any exist
                    if (category["categories"] != null)
                    {
                        foreach (JObject subcategory in (JArray)category["categories"])
                        {
                            // Loop through the achievments in the subcategory
                            foreach (JObject achievement in (JArray)subcategory["achievements"])
                            {
                                if (achievement["title"] != null)
                                {
                                    retval.Add(new Achievement((int)achievement["id"], (String)achievement["title"], (int)achievement["points"], (String)achievement["description"]));
                                }
                            }
                        }
                    }
                }

                return retval;
            }

            /// <summary>
            /// Returns the Battle.net RESTful API for the character and realm specified using the locale specified when constructing the object
            /// </summary>
            /// <param name="character">The name of the character</param>
            /// <param name="realm">The realm name in either standard or friendly format</param>
            /// <returns>String</returns>
            private String GetCharacterAPI(String character, String realm)
            {
                return String.Format("http://{0}/api/wow/character/{2}/{1}?locale={3}&fields=guild,stats,talents,items,reputation,titles,professions,companions,mounts,pets,achievements,progression,pvp,quests", this.APIBase, character, realm, _locale.ToString());
            }

            /// <summary>
            /// Creates a Character object using the data from the Battle.net RESTful API
            /// </summary>
            /// <param name="character">The name of the character to find</param>
            /// <param name="realm">The realm name in either standard or friendly format</param>
            /// <param name="request">The DatabaseRequest object to use when inflating achievmeent data, to inflate using the RESTful API pass null</param>
            /// <returns>A Character</returns>
            public Character GetCharacter(String character, String realm, DatabaseRequest request)
            {
                Character retval = new Character();

                try
                {
                    realm = realm.Replace("'", "").Replace("`", "").Replace(" ", "-");
                    JObject json = JObject.Parse(this.GetJSON(this.GetCharacterAPI(character, realm)));

                    if (json.HasValues)
                    {
                        retval = new Character((String)json["name"], (String)json["realm"], (short)json["class"], (short)json["race"], ((int)json["gender"] == 1), (short)json["level"], (int)json["achievementPoints"], DateTime.Parse("1970-01-01").AddMilliseconds((long)json["lastModified"]), false, (string)json["thumbnail"], _region);

                        // If there is a guild token, parse it into the character object
                        if (json["guild"] != null)
                        {
                            retval.ParseGuildData(json["guild"]);
                        }

                        // If the player has unlocked any titles parse them into the character object
                        if (json["titles"] != null)
                        {
                            retval.ParseTitleData(json["titles"]);
                        }

                        // If there is any achievement data parse it into the character object
                        if (json["achievements"] != null)
                        {
                            if (request == null)
                            {
                                retval.ParseAchievementData(json["achievements"], this);
                            }
                            else
                            {
                                retval.ParseAchievementData(json["achievements"], request);
                            }
                        }
                    }
                }
                catch { }

                return retval;
            }

            /// <summary>
            /// Returns the Battle.net RESTful API for the guild and realm specified using the locale specified when constructing the object
            /// </summary>
            /// <param name="name">The name of the guild</param>
            /// <param name="realm">The realm name in either standard or friendly format</param>
            /// <returns>String</returns>
            public String GetGuildAPI(String name, String realm)
            {
                String retval = String.Empty;

                realm = realm.Replace("'", String.Empty).Replace("`", String.Empty).Replace(" ", "-");
                retval = String.Format("http://{0}/api/wow/guild/{1}/{2}?fields=members", this.APIBase, realm, name.Replace(" ", "%20"));

                return retval;
            }

            /// <summary>
            /// Creates a Guild object using the data from the Battle.net RESTful API
            /// </summary>
            /// <param name="name">The name of the guild</param>
            /// <param name="realm">The realm name in either standard or friendly format</param>
            /// <returns>A Guild</returns>
            public Guild GetGuild(String name, String realm)
            {
                Guild retval = new Guild();
                try
                {
                    realm = realm.Replace("'", String.Empty).Replace("`", String.Empty).Replace(" ", "-");
                    JObject json = JObject.Parse(this.GetJSON(this.GetGuildAPI(name, realm)));

                    if (json.HasValues)
                    {
                        retval = new Guild((String)json["name"], (String)json["realm"], (short)json["level"], this.GetTokenCount(json["members"]), (int)json["achievementPoints"]);
                    }
                }
                catch { }

                return retval;
            }

            /// <summary>
            /// Returns the Battle.net RESTful API for the realm specified using the locale specified when construction the object
            /// </summary>
            /// <param name="name">The name of the realm</param>
            /// <returns>String</returns>
            public String GetRealmAPI(String name)
            {
                name = name.Replace("'", String.Empty).Replace("`", String.Empty).Replace(" ", "-");
                return String.Format("http://{0}/api/wow/realm/status?realms={1}&locale={2}", this.APIBase, name, _locale.ToString());
            }

            /// <summary>
            /// Creates a Realm object using the data from the Battle.net RESTful API
            /// </summary>
            /// <param name="name">The name of the realm</param>
            /// <returns>A Realm</returns>
            public Realm GetRealm(String name)
            {
                Realm retval = new Realm();
                name = name.Replace("'", String.Empty).Replace("`", String.Empty).Replace(" ", "-");
                JObject obj = JObject.Parse(this.GetJSON(this.GetRealmAPI(name)));

                if (obj.HasValues)
                {
                    JToken json = obj["realms"].First;
                    retval = new Realm((string)json["type"], (bool)json["queue"], (bool)json["status"], (string)json["population"], (string)json["name"], (string)json["battlegroup"], (string)json["slug"]);
                }

                return retval;
            }

        #endregion

        #region Constructors

            public WebRequest(BattleNetRegion region, BattleNetLocale locale)
            {
                _region = region;
                _locale = locale;
            }

        #endregion
    }
}
